package com.zcxy.mybatis.util.mapper;

import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zcxy.exception.BusinessException;
import com.zcxy.mybatis.util.bean.InverseJoinColumnen;
import com.zcxy.mybatis.util.bean.JoinColumnen;
import com.zcxy.mybatis.util.bean.Relation;
import com.zcxy.mybatis.util.enums.FieldCollectionType;
import com.zcxy.util.BeanCopierUtil;
import lombok.Data;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

/**
 * AbstractAutoMapper for one2one/one2many/many2many
 *
 * @author zcc
 */
@Data
public abstract class AbstractAutoMapper {
    @Autowired
    ObjectFactory<SqlSession> factory;


    private Map<String, List<Relation>> r1 = new HashMap<>();

    public <T, E> void relationCommon(List<T> rL, Relation r, QueryWrapper... qWs) {
        List<JoinColumnen> $_3 = r.getJoinColumnens();
        Class<?> Q1 = r.getMapper();
        BaseMapper<E> $_1 = (BaseMapper<E>) factory.getObject().getMapper(Q1);

        QueryWrapper qw = getQueryWrapper(r.getMapperClazz(), qWs);
        QueryWrapper $_2 = qw == null ? new QueryWrapper() : qw;
        $_3.stream().forEach(i -> {
            List<Object> $_4 = rL.stream().map(k -> {
                try {
                    Field $_5 = k.getClass().getDeclaredField(camelCaseName(i.getName()));
                    $_5.setAccessible(true);
                    return (Serializable) $_5.get(k);
                } catch (Exception e) {
                    throw new BusinessException("refProperty/refPropertyValue one to many id is not correct!");
                }
            }).collect(Collectors.toList());
            if (StringUtils.isEmpty(i.getReferencedColumnName())) {
                i.setReferencedColumnName(HumpToUnderline(r.getTableId()));

            }
            $_2.in(i.getReferencedColumnName(), $_4);
        });
        List<E> list = $_1.selectList($_2);
        //将值设置进入属性
        setValueToVo(r, rL, list);
    }

    public <T, E> void oneToMany(List<T> rL, Relation r, QueryWrapper... $_2) {
        relationCommon(rL, r, $_2);
    }

    public <T, E> void setValueToVo(Relation r, List<T> t, List<E> e) {
        t.stream().forEach(i -> {
            if (CollectionUtil.isNotEmpty(e)) {
                List<E> es = e.stream().filter(k -> {
                    boolean ret = false;
                    int num = 0;
                    for (JoinColumnen joinColumnen : r.getJoinColumnens()) {
                        try {
                            Field $_6 = i.getClass().getDeclaredField(camelCaseName(joinColumnen.getName()));
                            Field $_7 = k.getClass().getDeclaredField(camelCaseName(joinColumnen.getReferencedColumnName()));
                            $_6.setAccessible(true);
                            $_7.setAccessible(true);
                            if ($_6.getType() == String.class) {
                                if ($_6.get(i).equals($_7.get(k))) {
                                    num++;
                                }
                            } else {
                                if ($_6.get(i) == $_7.get(k)) {
                                    num++;
                                }
                            }
                            $_6.setAccessible(false);
                            $_7.setAccessible(false);
                        } catch (NoSuchFieldException ex) {
                            ex.printStackTrace();
                        } catch (IllegalAccessException ex) {
                            ex.printStackTrace();
                        }
                    }
                    return num == r.getJoinColumnens().size();
                }).collect(Collectors.toList());
                Field $_6 = null;
                try {
                    $_6 = i.getClass().getDeclaredField(r.getFiledName());
                    $_6.setAccessible(true);
                    // list to set
                    if (r.getFieldCollectionType() == FieldCollectionType.SET) {
                        if (r.getMapperClazz() != r.getEntityClazz()) {
                            $_6.set(i, BeanCopierUtil.copyListToSet(es, r.getEntityClazz()));
                        } else {
                            Set<E> set = new HashSet<E>();
                            for (E e1 : es) {// list 被访问，导致延迟立即加载，延迟失败！
                                set.add(e1);
                            }
                            $_6.set(i, set);
                        }
                    } else if (r.getFieldCollectionType() == FieldCollectionType.LIST) {
                        if (r.getMapperClazz() != r.getEntityClazz()) {
                            $_6.set(i, BeanCopierUtil.copyListToList(es, r.getEntityClazz()));
                        } else {
                            $_6.set(i, es);
                        }
                    } else {
                        if (es.size() > 0) {
                            if (r.getMapperClazz() != r.getEntityClazz()) {
                                $_6.set(i, BeanCopierUtil.copyObject(es.get(0), r.getEntityClazz()));
                            } else {
                                $_6.set(i, es.get(0));
                            }

                        }
                    }
                    $_6.setAccessible(false);
                } catch (Exception e2) {
                    throw new RuntimeException(
                            String.format("{0} call setter {1} is not correct!", r, $_6.getName()));
                }
            }


        });

    }


    public String HumpToUnderline(String para) {
        StringBuilder sb = new StringBuilder(para);
        int temp = 0;
        if (!para.contains("_")) {
            for (int i = 0; i < para.length(); i++) {
                if (Character.isUpperCase(para.charAt(i))) {
                    sb.insert(i + temp, "_");
                    temp += 1;
                }
            }
        }
        return sb.toString().toLowerCase();
    }

    public String camelCaseName(String underscoreName) {
        StringBuilder result = new StringBuilder();
        if (underscoreName != null && underscoreName.length() > 0) {
            boolean flag = false;
            for (int i = 0; i < underscoreName.length(); i++) {
                char ch = underscoreName.charAt(i);
                if ("_".charAt(0) == ch) {
                    flag = true;
                } else {
                    if (flag) {
                        result.append(Character.toUpperCase(ch));
                        flag = false;
                    } else {
                        result.append(ch);
                    }
                }
            }
        }
        return result.toString();
    }

    public <T> void oneToMany(T t, Relation r, QueryWrapper... $_2) {
        List<T> ts = new ArrayList<>();
        ts.add(t);
        this.oneToMany(ts, r, $_2);
    }

    public <T> void ManyToOne(List<T> rL, Relation r, QueryWrapper $_2) {
        relationCommon(rL, r, $_2);
    }

    public <T> void ManyToOne(T t, Relation r, QueryWrapper... $_2) {
        List<T> ts = new ArrayList<>();
        ts.add(t);
        this.ManyToOne(ts, r, $_2);
    }

    public <T> void oneToOne(List<T> rL, Relation r, QueryWrapper... $_2) {
        relationCommon(rL, r, $_2);
    }

    public <T> void oneToOne(T t, Relation r, QueryWrapper... $_2) {
        List<T> ts = new ArrayList<>();
        ts.add(t);
        this.oneToOne(ts, r, $_2);
    }

    public <T, M, E> void manyToMany(List<T> rL, Relation r, QueryWrapper... qws) {
        List<JoinColumnen> $_3 = r.getJoinColumnens();
        Class<?> Q1 = r.getJoinTableEn().getRelationMapper();
        BaseMapper<M> $_1 = (BaseMapper<M>) factory.getObject().getMapper(Q1);
        QueryWrapper q1 = getQueryWrapper(r.getJoinTableEn().getRelationClazz(), qws);
        QueryWrapper $_2 = q1 == null ? new QueryWrapper() : q1;
        $_3.stream().forEach(i -> {
            List<Object> $_4 = rL.stream().map(k -> {
                try {
                    Field $_5 = k.getClass().getDeclaredField(i.getName());
                    $_5.setAccessible(true);
                    return (Serializable) $_5.get(k);
                } catch (Exception e) {
                    throw new BusinessException("refProperty/refPropertyValue one to many id is not correct!");
                }
            }).collect(Collectors.toList());
            if (StringUtils.isEmpty(i.getReferencedColumnName())) {
                i.setReferencedColumnName(HumpToUnderline(r.getTableId()));

            }
            $_2.in(i.getReferencedColumnName(), $_4);
        });
        List<M> listM = $_1.selectList($_2);
        if (CollectionUtil.isEmpty(listM)) {
            return;
        }
        QueryWrapper qw = getQueryWrapper(r.getMapperClazz(), qws);

        QueryWrapper queryWrapperM = qw == null ? new QueryWrapper() : qw;
        List<InverseJoinColumnen> inverseJoinColumnens = r.getInverseJoinColumns();
        inverseJoinColumnens.stream().forEach(i -> {
            List<Object> $_4 = listM.stream().map(k -> {
                try {
                    Field $_5 = k.getClass().getDeclaredField(camelCaseName(i.getName()));
                    $_5.setAccessible(true);
                    return (Serializable) $_5.get(k);
                } catch (Exception e) {
                    throw new BusinessException("refProperty/refPropertyValue one to many id is not correct!");
                }
            }).collect(Collectors.toList());
            if (StringUtils.isEmpty(i.getReferencedColumnName())) {
                i.setReferencedColumnName(HumpToUnderline(r.getTableId()));
            }
            queryWrapperM.in(i.getReferencedColumnName(), $_4);
        });

        Class<?> mapperClassE = r.getMapper();
        BaseMapper<M> mapperE = (BaseMapper<M>) factory.getObject().getMapper(mapperClassE);
        List<E> es = mapperE.selectList(queryWrapperM);
        setValueToVoManyToMany(r, rL, listM, es);

    }

    public <T, M, E> void setValueToVoManyToMany(Relation r, List<T> t, List<M> m, List<E> e) {
        t.stream().forEach(i -> {
            List<M> ms = m.stream().filter(k -> {
                int num = 0;
                for (JoinColumnen joinColumnen : r.getJoinColumnens()) {
                    try {
                        Field $_6 = i.getClass().getDeclaredField(camelCaseName(joinColumnen.getName()));
                        Field $_7 = k.getClass().getDeclaredField(camelCaseName(joinColumnen.getReferencedColumnName()));
                        $_6.setAccessible(true);
                        $_7.setAccessible(true);
                        if ($_6.getType() == String.class) {
                            if ($_6.get(i).equals($_7.get(k))) {
                                num++;
                            }
                        } else {
                            if ($_6.get(i) == $_7.get(k)) {
                                num++;
                            }
                        }
                        $_6.setAccessible(false);
                        $_7.setAccessible(false);
                    } catch (NoSuchFieldException ex) {
                        ex.printStackTrace();
                    } catch (IllegalAccessException ex) {
                        ex.printStackTrace();
                    }
                }
                return num == r.getJoinColumnens().size();
            }).collect(Collectors.toList());
            if (CollectionUtil.isEmpty(ms)) {
                return;
            }
            if (CollectionUtil.isNotEmpty(e)) {
                List<E> es = e.stream().filter(k -> {
                    boolean ret = false;
                    for (M j : ms) {
                        for (InverseJoinColumnen joinColumnen : r.getInverseJoinColumns()) {
                            try {
                                Field $_6 = j.getClass().getDeclaredField(camelCaseName(joinColumnen.getName()));
                                Field $_7 = k.getClass().getDeclaredField(camelCaseName(joinColumnen.getReferencedColumnName()));
                                $_6.setAccessible(true);
                                $_7.setAccessible(true);
                                if ($_6.getType() == String.class) {
                                    if ($_6.get(j).equals($_7.get(k))) {
                                        ret = true;
                                        break;
                                    }
                                } else {
                                    if ($_6.get(i) == $_7.get(k)) {
                                        ret = true;
                                        break;
                                    }
                                }
                                $_6.setAccessible(false);
                                $_7.setAccessible(false);
                            } catch (NoSuchFieldException ex) {
                                ex.printStackTrace();
                            } catch (IllegalAccessException ex) {
                                ex.printStackTrace();
                            }
                        }
                    }
                    return ret;
                }).collect(Collectors.toList());


                Field $_6 = null;
                try {
                    $_6 = i.getClass().getDeclaredField(r.getFiledName());
                    $_6.setAccessible(true);
                    // list to set
                    if (r.getFieldCollectionType() == FieldCollectionType.SET) {
                        if (r.getMapperClazz() != r.getEntityClazz()) {
                            $_6.set(i, BeanCopierUtil.copyListToSet(es, r.getEntityClazz()));
                        } else {
                            Set<E> set = new HashSet<E>();
                            for (E e1 : es) {
                                set.add(e1);
                            }
                            $_6.set(i, set);
                        }
                    } else if (r.getFieldCollectionType() == FieldCollectionType.LIST) {
                        if (r.getMapperClazz() != r.getEntityClazz()) {
                            $_6.set(i, BeanCopierUtil.copyListToList(es, r.getEntityClazz()));
                        } else {
                            $_6.set(i, es);
                        }
                    } else {
                        if (es.size() > 0) {
                            if (r.getMapperClazz() != r.getEntityClazz()) {
                                $_6.set(i, BeanCopierUtil.copyObject(es.get(0), r.getEntityClazz()));
                            } else {
                                $_6.set(i, es.get(0));
                            }

                        }
                    }
                    $_6.setAccessible(false);
                } catch (Exception e2) {
                    throw new RuntimeException(
                            String.format("{0} call setter {1} is not correct!", r, $_6.getName()));
                }
            }

        });
    }

    public <T> void manyToMany(T t, Relation r, QueryWrapper... $_2) {
        List<T> ts = new ArrayList<>();
        ts.add(t);
        this.manyToMany(ts, r, $_2);
    }

    public QueryWrapper getQueryWrapper(Class aClass, QueryWrapper... queryWrappers) {
        if (queryWrappers != null && queryWrappers.length > 0) {
            List<QueryWrapper> queryWrapper = null;
            try {
                queryWrapper = Arrays.asList(queryWrappers).stream().filter(i -> {
                    return i.getEntity().getClass() == aClass;
                }).collect(Collectors.toList());
            } catch (Exception e) {

                return null;
            }
            if (queryWrapper.size() > 0) {
                return queryWrapper.get(0);
            } else {
                return null;
            }

        }

        return null;
    }


}
